package com.artollkitplus.benchmark;

import java.nio.ByteBuffer;
import java.util.List;

import javax.microedition.khronos.opengles.GL10;

import com.artollkitplus.benchmark.R;

import android.content.res.Resources;
import android.graphics.PixelFormat;
import android.hardware.Camera;
import android.hardware.Camera.Parameters;
import android.hardware.Camera.PreviewCallback;
import android.hardware.Camera.Size;
import android.opengl.GLSurfaceView;
import android.util.Log;

public class CameraPreviewHandler implements PreviewCallback {
	private GLSurfaceView glSurfaceView;
	private OpenGLCamRenderer renderer;
	private Resources res;
	private int textureSize=256;
	private int previewFrameWidth=240;
	private int previewFrameHeight=160;
	private int bwSize=previewFrameWidth*previewFrameHeight;
	private int mode = Config.MODE_IMAGE_RGB;
	private Object modeLock = new Object();
	private MarkerInfo markerInfo;
	private ConversionWorker convWorker;
	public byte[] frame;
	
	public CameraPreviewHandler(GLSurfaceView glSurfaceView,
			OpenGLCamRenderer rend, Resources res, MarkerInfo markerInfo) {
		this.glSurfaceView = glSurfaceView;
		this.renderer = rend;
		this.res = res;
		this.markerInfo = markerInfo;
		convWorker = new ConversionWorker(rend);
	}
	
	static { 
	    System.loadLibrary( "yuv420sp2rgb" );	
	} 
	
	private native void yuv420sp2rgb(byte[] in, int width, int height, int textureSize, byte[] out);

	public void init(Camera camera) throws Exception {
		Parameters camParams = camera.getParameters();
		if (camParams.getPreviewFormat() != PixelFormat.YCbCr_420_SP) {
			throw new Exception(res.getString(R.string.error_unkown_pixel_format));
		}			
		Size previewSize = camParams.getPreviewSize();
		previewFrameWidth = previewSize.width;
		previewFrameHeight = previewSize.height;
		textureSize = (int) Math.pow(2, Math.ceil(Math.log((double)Math.max(previewFrameWidth, previewFrameHeight))/Math.log(2)));
		CurrentConfig.DEFAULT_IMAGE_WIDTH = previewSize.width;
		CurrentConfig.DEFAULT_IMAGE_HEIGT = previewSize.height;
		bwSize = previewFrameWidth * previewFrameHeight;
		frame = new byte[bwSize*3];
		for (int i = 0; i < frame.length; i++) {
			frame[i]=(byte) 128;
		}
		renderer.setPreviewFrameSize(textureSize, previewFrameWidth, previewFrameHeight);
		markerInfo.setImageSize(previewFrameWidth, previewFrameHeight);
	}

	public void onPreviewFrame(byte[] data, Camera camera) {
		if(CurrentConfig.USE_MENU_PAUSE_SCREEN){
			if(!CurrentConfig.MENU_PAUSE_SCREEN_ON){
				if (data == null)
					return;
				convWorker.nextFrame(data);
				markerInfo.detectMarkers(data);
			}
		}else{
			if (data == null)
				return;
			convWorker.nextFrame(data);
			markerInfo.detectMarkers(data);
		}
	}

	protected void setMode(int pMode) {
		synchronized (modeLock) {
			this.mode = pMode;
			switch(mode) {
			case Config.MODE_IMAGE_RGB:
				renderer.setMode(GL10.GL_RGB);
				break;
			case Config.MODE_IMAGE_GRAY:
				renderer.setMode(GL10.GL_LUMINANCE);
				break;
			}
		}		
	}
	
	class ConversionWorker extends Thread {
		private byte[] curFrame;
		private OpenGLCamRenderer render;

		public ConversionWorker(OpenGLCamRenderer rend) {
			setDaemon(true);
			this.render = rend;
			start();
		}
		@Override
		public synchronized void run() {			
			try {
				wait();
			} catch (InterruptedException e) {}
			while(true) {
				render.getFrameLock().lock();
				synchronized (modeLock) {
					switch(mode) {
					case Config.MODE_IMAGE_RGB:
						yuv420sp2rgb(curFrame, previewFrameWidth, previewFrameHeight, textureSize, frame);   
						render.setNextFrame(ByteBuffer.wrap(frame));
						break;
					case Config.MODE_IMAGE_GRAY:
						render.setNextFrame(ByteBuffer.wrap(curFrame));	//looks like it's working
						break;
					}
				}
				render.getFrameLock().unlock();
				glSurfaceView.requestRender();
				try {
					wait();
				} catch (InterruptedException e) {}
			}
		}
		synchronized void nextFrame(byte[] frame) {
			if(this.getState() == Thread.State.WAITING) {
				curFrame = frame;
				this.notify();
			} 
		}
	}
	
	public static Size getOptimalPreviewSize(List<Size> sizes, int w, int h) {
	    final double ASPECT_TOLERANCE = 0.1;
	    final double MAX_DOWNSIZE = 1.5;

	    double targetRatio = (double) w / h;
	    if (sizes == null) return null;

	    Size optimalSize = null;
	    double minDiff = Double.MAX_VALUE;

	    int targetHeight = h;

	    for (Size size : sizes) {
	        double ratio = (double) size.width / size.height;
	        double downsize = (double) size.width / w;
	        if (downsize > MAX_DOWNSIZE) {
	            continue;
	        }
	        if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE) continue;
	        if (Math.abs(size.height - targetHeight) < minDiff) {
	            optimalSize = size;
	            minDiff = Math.abs(size.height - targetHeight);
	        }
	    }

	    if (optimalSize == null) {
	        minDiff = Double.MAX_VALUE;
	        for (Size size : sizes) {
	            double downsize = (double) size.width / w;
	            if (downsize > MAX_DOWNSIZE) {
	                continue;
	            }
	            if (Math.abs(size.height - targetHeight) < minDiff) {
	                optimalSize = size;
	                minDiff = Math.abs(size.height - targetHeight);
	            }
	        }
	    }
	    if (optimalSize == null) {
	        minDiff = Double.MAX_VALUE;
	        for (Size size : sizes) {
	            if (Math.abs(size.height - targetHeight) < minDiff) {
	                optimalSize = size;
	                minDiff = Math.abs(size.height - targetHeight);
	            }
	        }
	    }
	    return optimalSize;
	}
}
